package com.ams.protocol.rtp;

import java.nio.ByteBuffer;
import java.util.ArrayList;

import com.ams.io.network.connection.IFramer;
import com.ams.util.BufferUtils;

public class RtpSession implements IFramer {

    private class AssembleBuffer {
        private ByteBuffer[] buffers;
        private int lastFrameIndex = 0;

        public AssembleBuffer(int capacity) {
            buffers = new ByteBuffer[capacity];
        }

        public void set(int index, ByteBuffer buf) {
            int capacity = buffers.length;
            if (index >= capacity) {
                ByteBuffer[] tmp = new ByteBuffer[capacity * 2];
                System.arraycopy(buffers, 0, tmp, 0, capacity);
                buffers = tmp;
            }
            buffers[index] = buf;
        }

        public void lastFrame(int index) {
            lastFrameIndex = index;
        }

        public boolean hasLostFrame() {
            for (int i = 0; i <= lastFrameIndex; i++) {
                if (buffers[i] == null)
                    return true;
            }
            return false;
        }

        public ByteBuffer[] getFrames() {
            ByteBuffer[] buf = new ByteBuffer[lastFrameIndex + 1];
            System.arraycopy(buffers, 0, buf, 0, buf.length);
            return buf;
        }

        public void clear() {
            for (int i = 0, len = buffers.length; i < len; i++) {
                buffers[i] = null;
            }
            lastFrameIndex = 0;
        }
    }

    private AssembleBuffer readFrameBuffer = new AssembleBuffer(1024);

    private long readSessionId = 0;
    private long writeSessionId = System.currentTimeMillis();

    private ByteBuffer[] getFrames() {
        return readFrameBuffer.getFrames();
    }

    public ByteBuffer[] parseFrame(ByteBuffer frame) {
        RtpPacket packet = new RtpPacket(frame);
        if (readSessionId == 0) {
            readSessionId = packet.syncSourceId;
        }

        long sessionId = packet.syncSourceId;
        if (sessionId < readSessionId) {
            // drop frame
            return null;
        }
        if (sessionId > readSessionId) {
            readSessionId = sessionId;
            readFrameBuffer.clear();
        }

        readFrameBuffer.set(packet.sequenceNum, packet.payload);

        if (packet.payloadType == 1) { // last frame?
            readFrameBuffer.lastFrame(packet.sequenceNum);
            if (!readFrameBuffer.hasLostFrame()) {
                return getFrames();
            }
            readSessionId = 0;
            readFrameBuffer.clear();
        }
        return null;
    }

    public ByteBuffer[] buildFrame(ByteBuffer[] buf) {
        ArrayList<RtpPacket> packets = new ArrayList<RtpPacket>();
        int sequnceNum = 0;
        int i = 0;
        writeSessionId++;
        RtpPacket packet = null;
        ByteBuffer payload = null;
        while (i < buf.length) {
            if (packet == null) {
                packet = new RtpPacket();
                packet.syncSourceId = writeSessionId;
                packet.payloadType = 0;
                packet.sequenceNum = sequnceNum++;
                payload = packet.getPayload();
            }

            ByteBuffer data = buf[i];
            int remaining = payload.remaining();
            if (remaining >= data.remaining()) {
                payload.put(data);
                i++;
            } else {
                if (remaining > 0) {
                    ByteBuffer slice = BufferUtils.trim(data, remaining);
                    payload.put(slice);
                }

                packets.add(packet);
                packet = null;
            }
        }
        if (packet != null) {
            packets.add(packet);
        }

        ByteBuffer[] frames = new ByteBuffer[packets.size()];
        for (int index = 0, len = frames.length; index < len; index++) {
            RtpPacket pkt = packets.get(index);
            if (index == len - 1) {
                pkt.payloadType = 1;
            }
            pkt.build();
            frames[index] = pkt.getFrame();
            frames[index].flip();
        }
        return frames;
    }

}
